<!DOCTYPE html>
<html>

<head>
    <title>Customize your own components</title>
    <script type="text/javascript" src="https://unpkg.com/dat.gui@0.7.6/build/dat.gui.min.js"></script>
    <link type="text/css" rel="stylesheet" href="https://unpkg.com/maptalks/dist/maptalks.css">
    <script type="text/javascript" src="https://unpkg.com/maptalks/dist/maptalks.js"></script>
    <script type="text/javascript" src="https://unpkg.com/three@0.138.0/build/three.min.js"></script>
    <script type="text/javascript"
        src="https://unpkg.com/three@0.138.0/examples/js/libs/stats.min.js"></script>
    <!-- <script type="text/javascript" src="https://unpkg.com/lz-string@1.4.4/libs/lz-string.min.js"></script> -->
    <script type="text/javascript" src="https://unpkg.com/maptalks.three@latest/dist/maptalks.three.js"></script>
    <script type="text/javascript" src="js/geoutil.js"></script>
    <!-- <script type="text/javascript" src="buildings.js"></script> -->
    <style>
        html,
        body {
            margin: 0px;
            height: 100%;
            width: 100%;
        }

        #map {
            width: 100%;
            height: 100%;
            background-color: #000;
        }
    </style>
</head>

<body>
    <div id="map"></div>
    <script>

        var map = new maptalks.Map("map", {
            center: [115.63373210417001, 34.87688357015699],
            zoom: 4,
            // pitch: 70,
            // bearing: 180,
            centerCross: true,
            doubleClickZoom: false,
            // baseLayer: new maptalks.TileLayer('tile', {
            //     urlTemplate: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
            //     subdomains: ['a', 'b', 'c', 'd'],
            //     attribution: '&copy; <a href="http://osm.org">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/">CARTO</a>'
            // })
        });

        // the ThreeLayer to draw buildings
        var threeLayer = new maptalks.ThreeLayer('t', {
            forceRenderOnMoving: true,
            forceRenderOnRotating: true,
            // animation: true
        });
        var stats;
        threeLayer.prepareToDraw = function (gl, scene, camera) {
            stats = new Stats();
            stats.domElement.style.zIndex = 100;
            document.getElementById('map').appendChild(stats.domElement);

            addLines();
        };
        threeLayer.addTo(map);


        var material = new THREE.LineBasicMaterial({
            linewidth: 1,
            color: '#fff',
            opacity: 0.2,
            transparent: true,
            blending: THREE.AdditiveBlending
        });

        var lineMaterial = new THREE.LineBasicMaterial({
            linewidth: 1,
            color: '#0d37ff',
            opacity: 0.1,
            transparent: true,
            blending: THREE.AdditiveBlending
        });
        var lineTrails;

        function addLines() {
            // data from https://github.com/maptalks/maptalks.odline
            fetch('./data/lines.json').then(function (res) {
                return res.json();
            }).then(function (geojson) {
                var lineStrings = geojson.map(function (feature) {
                    var coordinates = feature.coordinates;
                    var [from, to] = coordinates;
                    var lnglats = [[parseFloat(from[0]), parseFloat(from[1])], [parseFloat(to[0]), parseFloat(to[1])]]
                    return new maptalks.LineString(lnglats);
                });

                var timer = 'generate line time';
                console.time(timer);
                var list = [];
                lineStrings.forEach(lineString => {
                    list.push({
                        lineString,
                        len: lineLength(lineString)
                    });
                });
                list = list.sort(function (a, b) {
                    return b.len - a.len
                });

                var offset = 2000;
                lineTrails = list.slice(0, offset).map(d => {
                    var line = new LineTrail(d.lineString, {
                        chunkLength: d.len / 100,
                        trail: 5,
                        speed: 1,
                        altitude: 100,
                    }, material, threeLayer)
                    return line;
                });
                var lines = list.slice(0, offset).map(d => {
                    return threeLayer.toLine(d.lineString, {}, lineMaterial);
                });


                console.log('lines.length:', lineTrails.length);
                console.timeEnd(timer);
                threeLayer.addMesh(lines);
                threeLayer.addMesh(lineTrails);
                initGui();
                // threeLayer.config('animation', true);
                animation();
            })
        }

        function animation() {
            // layer animation support Skipping frames
            if (!map.isInteracting()) {
                threeLayer._needsUpdate = !threeLayer._needsUpdate;
                if (threeLayer._needsUpdate) {
                    threeLayer.redraw();
                }
            } else {
                threeLayer.redraw();
            }
            stats.update();
            requestAnimationFrame(animation);
        }


        function initGui() {
            var params = {
                add: true,
                color: material.color.getStyle(),
                show: true,
                opacity: material.opacity,
                altitude: 0
            };
            var gui = new dat.GUI();
            gui.add(params, 'add').onChange(function () {
                if (params.add) {
                    threeLayer.addMesh(lineTrails);
                } else {
                    threeLayer.removeMesh(lineTrails);
                }
            });
            gui.addColor(params, 'color').name('line color').onChange(function () {
                material.color.set(params.color);
                lineTrails.forEach(function (mesh) {
                    mesh.setSymbol(material);
                });
            });
            gui.add(params, 'opacity', 0, 1).onChange(function () {
                material.opacity = params.opacity;
                lineTrails.forEach(function (mesh) {
                    mesh.setSymbol(material);
                });
            });
            gui.add(params, 'show').onChange(function () {
                lineTrails.forEach(function (mesh) {
                    if (params.show) {
                        mesh.show();
                    } else {
                        mesh.hide();
                    }
                });
            });
            gui.add(params, 'altitude', 0, 300).onChange(function () {
                lineTrails.forEach(function (mesh) {
                    mesh.setAltitude(params.altitude);
                });
            });
        }


        //default values
        var OPTIONS = {
            trail: 5,
            chunkLength: 50,
            speed: 1,
            altitude: 0,
            interactive: false
        };

        const MAX_POINTS = 1000;

        /**
         * custom component
         * */

        class LineTrail extends maptalks.BaseObject {
            constructor(lineString, options, material, layer) {
                options = maptalks.Util.extend({}, OPTIONS, options, { layer, lineString });
                super();
                //Initialize internal configuration
                // https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L135
                this._initOptions(options);

                const { altitude, chunkLength, speed, trail } = options;
                const chunkLines = lineSlice(lineString, chunkLength);


                const centerPt = layer.coordinateToVector3(lineString.getCenter());
                //cache position for  faster computing,reduce double counting
                const positionMap = {};
                for (let i = 0, len = chunkLines.length; i < len; i++) {
                    const chunkLine = chunkLines[i];
                    for (let j = 0, len1 = chunkLine.length; j < len1; j++) {
                        const lnglat = chunkLine[j];
                        const key = lnglat.join(',').toString();
                        if (!positionMap[key]) {
                            positionMap[key] = layer.coordinateToVector3(lnglat).sub(centerPt);
                        }
                    }
                }

                const positions = getChunkLinesPosition([chunkLines[0]], layer, positionMap, centerPt).positions;
                const geometry = new THREE.BufferGeometry();
                const ps = new Float32Array(MAX_POINTS * 3); // 3 vertices per point
                geometry.addAttribute('position', new THREE.BufferAttribute(ps, 3).setDynamic(true));
                setLineGeometryAttribute(geometry, positions);
                this._createLine(geometry, material);

                //set object3d position
                const z = layer.distanceToVector3(altitude, altitude).x;

                const center = lineString.getCenter();
                const v = layer.coordinateToVector3(center, z);
                this.getObject3d().position.copy(v);

                this._params = {
                    trail: Math.max(1, trail),
                    index: 0,
                    len: chunkLines.length,
                    chunkLines,
                    layer,
                    speed: Math.min(1, speed),
                    idx: 0,
                    positions: [],
                    positionMap,
                    centerPt
                };
                // this._init();
            }


            _init() {
                const { len, chunkLines, layer, trail, positionMap, centerPt } = this._params;
                for (let i = 0; i < len; i++) {
                    const result = chunkLines.slice(i, i + trail);
                    const ps = getChunkLinesPosition(result, layer, positionMap, centerPt).positions;
                    this._params.positions[i] = ps;
                }
            }


            _animation() {
                const { index, positions, idx, speed, len, chunkLines, layer, trail, positionMap, centerPt } = this._params;
                const i = Math.round(index);
                if (i > idx) {
                    this._params.idx++;
                    let ps = positions[i];
                    if (!ps) {
                        const result = chunkLines.slice(i, i + trail);
                        ps = getChunkLinesPosition(result, layer, positionMap, centerPt).positions;
                        this._params.positions[i] = ps;
                    }
                    setLineGeometryAttribute(this.getObject3d().geometry, ps);
                    this.getObject3d().geometry.attributes.position.needsUpdate = true;
                }
                if (index >= len) {
                    this._params.index = -1;
                    this._params.idx = -1;
                }
                this._params.index += speed;
            }
        }
    </script>
</body>

</html>